﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Xml.Linq;
using CBTM.Core;
using LAIR.ResourceAPIs.WordNet;

namespace CBTM.Processor
{
    public class FileEngine
    {
        #region Create Tables

        #region Create T Table

        // tạo file chứa danh sách từ trong CSDL của WordNet
        private void CreateWordIndex(string wn_index)
        {
            List<string> lstIndex = new List<string>();
            foreach (string offset in Global.WNEngine.GetListOffsets())
                if (offset.Substring(0, offset.IndexOf(':')) == "Noun")
                    foreach (string word in Global.WNEngine.GetSynSet(offset).Words)
                        if (!lstIndex.Contains(word))
                            lstIndex.Add(word);
            lstIndex.Sort();
            Console.WriteLine("Word list retrieved.");

            StreamWriter sw = new StreamWriter(wn_index);
            foreach (string word in lstIndex)
                sw.WriteLine(word);
            sw.Close();
            Console.WriteLine("wnindex.dat created");
        }

        // tạo TTable1
        private void CreateTTable1(string mrd, string wn_words_index, string t_table_1)
        {
            // lấy danh sách từ tiếng Anh của wn-index.txt
            List<string> lstIndex = new List<string>();
            StreamReader sr = new StreamReader(wn_words_index);
            while (sr.Peek() >= 0)
                lstIndex.Add(sr.ReadLine());
            sr.Close();

            // lấy từ điển Anh-Việt
            XElement rootDic = XElement.Load(mrd);

            // ghi ra TTable
            XElement rootTable = new XElement("TTable");
            foreach (XElement word in rootDic.Elements())
            {
                string[] argsUnigram = word.Element("HEAD").Value.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
                string head = String.Join("_", argsUnigram).ToLower();
                if (lstIndex.Contains(head))
                {
                    XElement node = new XElement("Node");
                    XElement en = new XElement("English", head);
                    node.Add(en);
                    foreach (XElement pos in word.Elements("POS"))
                    {
                        string posVal = pos.FirstAttribute.Value;
                        if (posVal.ToLower() == "từ viết tắt" || posVal.ToLower() == "noun")
                            foreach (XElement body in pos.Elements())
                            {
                                //XElement vi = new XElement("Vietnamese", body.Value);
                                node.Add(new XElement("Vietnamese", body.Value));
                            }
                    }
                    rootTable.Add(node);
                }
            }
            rootTable.Save(t_table_1);
        }

        // xóa các nút không tồn tại cách dịch
        private void ModifyTTable1(string t_table_1, string t_table_2)
        {
            XElement root = XElement.Load(t_table_1);
            var nodes = root.Elements();
            //int count = nodes.Count();
            for (int i = nodes.Count(); --i >= 0; )
            {
                //Console.WriteLine(i);
                XElement node = nodes.ElementAt(i);
                if (node.Elements().Count() < 2)
                    node.Remove();
            }
            root.Save(t_table_2);
        }

        // merge các nút bị trùng head
        private void ModifyTTable2(string t_table_2, string t_table_3)
        {
            Dictionary<string, List<string>> dic = new Dictionary<string, List<string>>();
            XElement root = XElement.Load(t_table_2);
            int count = 0;
            foreach (XElement t in root.Elements())
            {
                string en = t.Element("English").Value;
                IEnumerable<XElement> vis = t.Elements("Vietnamese");
                int tmpcount = vis.Count();
                List<string> lstTranslation;
                if (!dic.ContainsKey(en))
                {
                    lstTranslation = new List<string>();
                    foreach (XElement vi in vis)
                        if (!lstTranslation.Contains(vi.Value) && vi.Value.Trim() != string.Empty)
                            lstTranslation.Add(vi.Value);
                    dic.Add(en, lstTranslation);
                }
                else
                {
                    lstTranslation = dic[en];
                    foreach (XElement vi in vis)
                        if (!lstTranslation.Contains(vi.Value) && vi.Value.Trim() != string.Empty)
                            lstTranslation.Add(vi.Value);
                    dic[en] = lstTranslation;
                    Console.WriteLine(en + " was found again at " + count);
                }
                Console.WriteLine(count + " - " + en + " processed");
                ++count;
            }

            count = 0;
            root = new XElement("TTable");
            foreach (var kvp in dic)
            {
                XElement node = new XElement("T", new XElement("English", kvp.Key));
                foreach (string vi in kvp.Value)
                    node.Add(new XElement("Vietnamese", vi));
                root.Add(node);
                Console.WriteLine(count + " - " + kvp.Key + " added");
                ++count;
            }
            root.Save(t_table_3);
        }

        #endregion

        #region Create SC Table

        // tạo danh sách class code
        private void CreateCCDic(string core_wn, string wn_sense_index, string ccdic)
        {
            StreamReader sr;
            StreamWriter sw;

            // lấy thông tin từ sense.index
            Dictionary<string, string> sns_idx = new Dictionary<string, string>();
            sr = new StreamReader(wn_sense_index);
            while (sr.Peek() >= 0)
            {
                string line = sr.ReadLine();
                if (line == string.Empty) continue;
                string str = line.Substring(0, line.IndexOf(' '));
                line = line.Substring(line.IndexOf(' ') + 1);
                string offset = line.Substring(0, line.IndexOf(' '));
                sns_idx.Add(str, offset);
            }
            sr.Close();

            // tìm offset tương ứng với sensekey
            List<string> lstClasscode = new List<string>();
            sr = new StreamReader(core_wn);
            while (sr.Peek() >= 0)
            {
                string line = sr.ReadLine();
                if (line == string.Empty) continue;
                string senseKey = line.Substring(3, line.IndexOf(']') - 3);
                string cc = sns_idx[senseKey];
                if (!lstClasscode.Contains(cc))
                    lstClasscode.Add(cc);
            }
            sr.Close();
            lstClasscode.Sort();

            // ghi các offset tìm được vào classcodes.txt
            sw = new StreamWriter(ccdic);
            foreach (string offset in lstClasscode)
                sw.WriteLine(offset);
            sw.Close();
        }

        // chép các synset vào class code tương ứng của nó
        private void CreateCODic(string ccdic, string codic)
        {
            // lấy danh sách class code
            List<int> classcodes = new List<int>();
            StreamReader sr = new StreamReader(ccdic);
            while (sr.Peek() >= 0)
            {
                string line = sr.ReadLine();
                if (line == string.Empty) continue;
                classcodes.Add(int.Parse(line));
            }
            sr.Close();

            // ghi offset vào class code
            StreamWriter sw = new StreamWriter(codic);
            foreach (int offset in classcodes)
            {
                SynSet synset = Global.WNEngine.GetSynSet("Noun:" + offset);
                sw.Write('N' + offset.ToString("00000000") + '#');
                sw.Write(offset + ";");
                Misc.Traverse(synset, classcodes, sw);
                sw.WriteLine();
            }
            sw.Close();
        }

        private void CreateSCTable(string codic, string sc_table)
        {
            StreamReader sr = new StreamReader(codic);
            XElement root = new XElement("SCTable");
            while (sr.Peek() >= 0)
            {
                string line = sr.ReadLine();
                if (line == string.Empty) continue;
                XElement sc = new XElement("SC", new XAttribute("ClassCode", line.Substring(0, line.IndexOf('#'))));
                List<string> offsets = new List<string>();
                foreach (string offset in (line.Substring(line.IndexOf('#') + 1)).Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries))
                {
                    string str = int.Parse(offset).ToString("00000000");
                    if (!offsets.Contains(str))
                        offsets.Add(str);
                    offsets.Sort();
                }
                foreach (string offset in offsets)
                    sc.Add(new XElement("Offset", offset));
                root.Add(sc);
            }
            var reverse = root.Elements("SC").Reverse();
            XElement newroot = new XElement("SCTable");
            foreach (XElement cc in reverse)
                newroot.Add(cc);
            newroot.Save(sc_table);
        }

        #endregion

        #region Create BST Table

        public static void CreateBSTTable1(Dictionary<string, float> p_list, string bst_table_1)
        {
            // chuyển về dạng có thể ghi được ra file
            Dictionary<string, List<string>> dicTmp = new Dictionary<string, List<string>>();
            foreach (var kvp in p_list)
            {
                string key = kvp.Key;
                int idx = key.IndexOf(':');
                string v = key.Substring(0, idx);
                string ek = key.Substring(idx + 1);
                List<string> lst = new List<string>();
                if (!dicTmp.TryGetValue(ek, out lst))
                    lst = new List<string>();
                lst.Add(v + ":" + kvp.Value);
                if (lst.Count > 1)
                    dicTmp[ek] = lst;
                else
                    dicTmp.Add(ek, lst);
            }

            // ghi kết quả lên file
            XElement root = new XElement("Root");
            foreach (var kvp in dicTmp)
            {
                XElement node = new XElement("Node", new XAttribute("Sense", kvp.Key));
                //int count = kvp.Value.Count;
                for (int i = -1; ++i < kvp.Value.Count; )
                {
                    //string[] arg = kvp.Value[i].Split(':');
                    node.Add(new XElement("Translation", kvp.Value[i]));
                }
                root.Add(node);
            }
            root.Save(bst_table_1);
        }

        private void ModifyBSTTable1(string bst_table_1, string bst_table_2)
        {
            XElement root = XElement.Load(bst_table_1);
            // giữ lại cách dịch có xác suất cao nhất
            foreach (XElement bst in root.Elements())
            {
                float val = 0;
                IEnumerable<XElement> translations = bst.Elements();
                foreach (XElement translation in translations)
                {
                    string[] args = translation.Value.Split(':');
                    float tmpVal = float.Parse(args[1]);
                    if (tmpVal > val)
                        val = tmpVal;
                }
                for (int i = translations.Count(); --i >= 0; )
                {
                    XElement removeTranslation = translations.ElementAt(i);
                    if (float.Parse(removeTranslation.Value.Split(':')[1]) < val)
                        removeTranslation.Remove();
                }
            }
            root.Save(bst_table_2);
        }

        private void ModifyBSTTable2(string bst_table_2, string bst_table_3)
        {
            // gom cách dịch của 1 từ tiếng Anh
            Dictionary<string, List<string>> dic = new Dictionary<string, List<string>>();
            XElement root = XElement.Load(bst_table_2);
            foreach (XElement bst in root.Elements())
            {
                string en = bst.FirstAttribute.Value.Split(':')[0];
                List<string> lst = new List<string>();
                dic.TryGetValue(en, out lst);
                if (lst == null)
                    lst = new List<string>();
                lst.Add(bst.Element("Translation").Value);
                if (lst.Count > 1)
                    dic[en] = lst;
                else
                    dic.Add(en, lst);
            }

            // sắp xếp theo thứ tự alphabet của từ tiếng Anh
            var sort = from kvp in dic
                       orderby kvp.Key ascending
                       select kvp;
            root = new XElement("Root");
            foreach (var kvp in sort)
            {
                XElement node = new XElement("Node", new XAttribute("Word", kvp.Key));
                foreach (string probability in kvp.Value)
                    node.Add(new XElement("Translation", probability));
                root.Add(node);
            }
            root.Save(bst_table_3);
        }

        #endregion

        #region Create BWN Table

        private void CreateBWNTable(WordNetEngine wn_engine, string bst_table_2, string bwn_table)
        {
            // trích xuất gloss của synset từ database
            //Dictionary<string, string> dicGloss = new Dictionary<string, string>();
            //StreamReader sr = new StreamReader("WNDB/data.noun");
            //while (sr.Peek() >= 0)
            //{
            //    string line = sr.ReadLine();
            //    if (line[0] == ' ') continue;
            //    dicGloss.Add(line.Substring(0, 8), line.Substring(line.IndexOf('|') + 2));
            //}
            //sr.Close();

            // lấy danh sách cách dịch từ TTable3.xml
            Dictionary<string, List<string>> dicTranslation = new Dictionary<string, List<string>>();
            XElement tXml = XElement.Load(Paths.TTable3);
            foreach (var t in tXml.Elements())
            {
                List<string> lst = new List<string>();
                foreach (var vi in t.Elements("Vietnamese"))
                    lst.Add(vi.Value);
                dicTranslation.Add(t.Element("English").Value, lst);
            }

            char[] separators = new char[] { ',', ' ', '-', '/', ';', '(', ')', '.', '\'' };
            Dictionary<string, List<string>> offsets = new Dictionary<string, List<string>>();
            XElement root = XElement.Load(bst_table_2);
            foreach (XElement bst in root.Elements())
            {
                string[] args = bst.FirstAttribute.Value.Split(':');
                string en = args[0];
                string offset = args[1];
                string translation = bst.Element("Translation").Value;
                translation = translation.Substring(0, translation.IndexOf(':'));
                foreach(string origin in dicTranslation[en])
                    if (string.Join("_", origin.Split(separators, StringSplitOptions.RemoveEmptyEntries)) == translation)
                    {
                        translation = origin;
                        break;
                    }
                if (!offsets.ContainsKey(offset))
                {
                    List<string> lstModified = new List<string>();
                    lstModified.Add(translation);
                    offsets.Add(offset, lstModified);
                }
                else
                    offsets[offset].Add(translation);
            }

            // Tạo kết quả VWNTable.xml
            var sort = from kvp in offsets
                       orderby kvp.Key
                       select kvp;
            root = new XElement("VWN");
            foreach (var kvp in sort)
            {
                XElement offset = new XElement("Offset");
                XElement id = new XElement("ID", kvp.Key);
                XElement words = new XElement("Words", String.Join(", ", wn_engine.GetSynSet("Noun:" + int.Parse(kvp.Key)).Words));
                //XElement gloss = new XElement("Gloss", dicGloss[kvp.Key].Trim());
                string str = string.Empty;
                str = string.Join(", ", kvp.Value);
                //str = string.Join("; ", GetOriginalTranslations(kvp.Value));
                str = Regex.Replace(str, @"(:[^,]*)", string.Empty);
                //foreach (string vi in kvp.Value)
                //    str += (vi.Substring(0, vi.IndexOf(':')) + ",");
                XElement trans = new XElement("Translation", str);
                //offset.Add(id, words, gloss, trans);
                offset.Add(id, words, trans);
                root.Add(offset);
            }
            root.Save(bwn_table);
        }

        private IEnumerable<string> GetOriginalTranslations(List<string> lstProbability/*, Dictionary<string, List<string>> originalTranslation*/)
        {
            foreach (string probability in lstProbability)
            {
                string modifiedTranslation = probability.Substring(0, probability.IndexOf(':'));
                yield return modifiedTranslation;
                //if(originalTranslation.TryGetValue())
            }
            //return from str in lstProbability
            //       select str.Substring(0, str.IndexOf(':'));
        }

        private Dictionary<string, List<string>> LoadOrginalTTable()
        {
            Dictionary<string, List<string>> dic = new Dictionary<string, List<string>>();

            return dic;
        }

        #endregion

        #endregion

        #region Load Tables

        private static Dictionary<string, List<string[]>> LoadTTable(string t_path)
        {
            char[] separators = new char[] { ',', ' ', '-', '/', ';', '(', ')', '.', '\'' };
            XElement ttable = XElement.Load(t_path);
            Dictionary<string, List<string[]>> table = new Dictionary<string, List<string[]>>();
            foreach (XElement t in ttable.Elements())
            {
                string en = t.Element("English").Value;
                List<string[]> trans = new List<string[]>();
                IEnumerable<XElement> elements = t.Elements("Vietnamese");
                foreach (XElement vi in t.Elements("Vietnamese"))
                    trans.Add(vi.Value.Split(separators, StringSplitOptions.RemoveEmptyEntries));
                foreach (SynSet synset in Global.WNEngine.GetSynSets(en, WordNetEngine.POS.Noun))
                    table.Add(en + ":" + synset.Offset.ToString("00000000"), trans);
            }
            Console.WriteLine("T Table loaded.");
            return table;
        }

        private static Dictionary<string, string> LoadSCTable(string sc_path)
        {
            Dictionary<string, string> table = new Dictionary<string, string>();
            XElement sctable = XElement.Load(sc_path);
            foreach (XElement sc in sctable.Elements())
            {
                string cc = sc.FirstAttribute.Value;
                //string offsets = string.Empty;
                string classcode = string.Empty;
                foreach (XElement offset in sc.Elements())
                {
                    string value = offset.Value;
                    //offsets += offset.Value + ";";
                    if (!table.TryGetValue(value, out classcode))
                        table.Add(value, cc);
                    //else
                    //    table[value] = cc;
                }
                //table.Add(cc, offsets);
            }
            Console.WriteLine("SC Table loaded.");
            return table;
        }

        #endregion

        #region Tasks

        // T Table sẽ được khởi tạo tương ứng với MRD đã chọn
        public void Prepare(string mrd)
        {
            if (!File.Exists(Paths.WNIndex))
                CreateWordIndex(Paths.WNIndex);
            CreateTTable1(mrd, Paths.WNIndex, Paths.TTable1);
            ModifyTTable1(Paths.TTable1, Paths.TTable2);
            ModifyTTable2(Paths.TTable2, Paths.TTable3);

            if (!File.Exists(Paths.CCDic))
                CreateCCDic(Paths.CoreWN, Paths.SenseIndex, Paths.CCDic);
            if (!File.Exists(Paths.CODic))
                CreateCODic(Paths.CCDic, Paths.CODic);
            CreateSCTable(Paths.CODic, Paths.SCTable);
        }

        public static void LoadTables(out Dictionary<string, string> sctable, out Dictionary<string, List<string[]>> ttable)
        {
            sctable = LoadSCTable(Paths.SCTable);
            ttable = LoadTTable(Paths.TTable3);

            //Dictionary<string, List<string>> dicCC = new Dictionary<string, List<string>>();
            //int count = Global.WNEngine.AllWords[WordNetEngine.POS.Noun].Count;
            //Summary(sctable, count);
        }

        public void Finish()
        {
            ModifyBSTTable1(Paths.BSTTable1, Paths.BSTTable2);
            ModifyBSTTable2(Paths.BSTTable2, Paths.BSTTable3);

            CreateBWNTable(Global.WNEngine, Paths.BSTTable2, Paths.VWNTable);
        }

        #endregion

        private static void Summary(Dictionary<string, string> sctable, int count)
        {
            Dictionary<string, List<string>> dic = new Dictionary<string, List<string>>(); // store class code words
            List<string> lst = new List<string>(); // store found words
            var offsets = Global.WNEngine.GetListOffsets();
            // iterate through class codes
            foreach (var cc in sctable)
            {
                string code = "Noun:" + int.Parse(cc.Key).ToString();
                if (!dic.ContainsKey(cc.Value))
                    dic.Add(cc.Value, new List<string>());
                foreach (var word in Global.WNEngine.GetSynSet(code).Words)
                {
                    if (!dic[cc.Value].Contains(word))
                        dic[cc.Value].Add(word);
                    if (!lst.Contains(word))
                        lst.Add(word);
                }
            }
            var newdic = from entry in dic
                         orderby entry.Value.Count
                         select new
                         {
                             entry.Key,
                             entry.Value.Count
                         };
            XElement root = XDocument.Load("Tables/Recall-SynsetsPerClasscode.xml").Root;
            foreach (var cc in newdic)
            {
                XElement node = root.Elements("ClassCode").Where(x => x.Element("Code").Value == cc.Key).FirstOrDefault();
                node.Add(new XElement("WordCount", cc.Count));
                node.Add(new XElement("WordPercent", ((decimal)cc.Count / count).ToString("0.000")));
            }
            root.Save("a.xml");
        }

        public static void CreateSCTable2()
        {
            Dictionary<string, List<string>> ttable = new Dictionary<string,List<string>>();
            XElement tXml = XElement.Load(Paths.TTable3);
            foreach (var t in tXml.Elements())
            {
                List<string> lstTranslation = new List<string>();
                foreach (var vi in t.Elements("Vietnamese"))
                    lstTranslation.Add(vi.Value);
                ttable.Add(t.Element("English").Value, lstTranslation);
            }

            Dictionary<string, List<string>> dicSC = new Dictionary<string, List<string>>();
            XElement scXml = XElement.Load(Paths.SCTable);
            foreach (var sc in scXml.Elements())
            {
                foreach (var offset in sc.Elements())
                {
                    foreach (string word in Global.WNEngine.GetSynSet("Noun:" + int.Parse(offset.Value).ToString()).Words)
                    {
                        foreach (string translation in ttable[word])
                        {
                            //if(!lst)
                        }
                    }
                }
            }
        }
    }
}